
import * as color from 'colors'
import * as Router from 'koa-router'
import { isNumber } from 'util'
import { GeneralController } from './controller/GeneralController'
import { LoginController } from './controller/LoginController'
import { StudentController } from './controller/StudentController'
import { SubjectController } from './controller/SubjectController'
import UploadController from './controller/UploadController'
import { ResponseUtil } from './helpers/ResponseUtil'
import { METHOD_METADATA, PATH_METADATA, REQUEST_ARGS_METADATA, RequestArgs } from './metadata'
import { KoaContext } from './server'

const router = new Router()

const controllerList = [
  StudentController,
  GeneralController,
  LoginController,
  UploadController,
  SubjectController
]

function mapRoute(Constructor: Constructor) {
  const instance = new Constructor()
  const prototype = Object.getPrototypeOf(instance)
  const root = Reflect.getMetadata(PATH_METADATA, Constructor) || ''
  return Object.getOwnPropertyNames(prototype).filter((prop) => {
    return prop !== 'constructor'
  }).filter((prop) => {
    const metadata = Reflect.getMetadata(PATH_METADATA, prototype[prop])
    return typeof prototype[prop] === 'function' && metadata
  }).map((methodName) => {
    const hander = prototype[methodName]
    // 取出定义的 metadata
    const path = root + Reflect.getMetadata(PATH_METADATA, hander)
    const method = Reflect.getMetadata(METHOD_METADATA, hander)
    return {
      path,
      method,
      hander,
      instance
    }
  })
}

type RouteHandler = (context: KoaContext<any, any>) => Promise<ApiTypes.ApiBaseResult>

function asyncWrap(controller: any, handler: RouteHandler): any {
  return async (context: KoaContext, next: any) => {
    try {
      const requestArgs = Reflect.getMetadata(REQUEST_ARGS_METADATA, handler)
      console.log('[ 获取元数据 ]')
      if (requestArgs) {
        const { query, body } = JSON.parse(requestArgs) as RequestArgs
        const queryObj = parseArgs(query)
        const bodyObj = parseArgs(body)
        const { msg, code } = checkArgs('query', context, queryObj)
        const { msg: m, code: c } = checkArgs('body', context, bodyObj)
        context.status = 200
        if (code === 0 && c === 0) {
          const response = await handler.call(controller, context as KoaContext)
          return context.body = response
        }
        return context.body = await ResponseUtil.fail(code || c, msg || m)
      } else {
        const response = await handler.call(controller, context as KoaContext)
        return context.body = response
      }
    } catch (error) {
      // 单纯的程序执行错误
      console.log(error)
      context.status = 500
      context.error = error
      return context.body = await ResponseUtil.serverError()
    } finally {
      await next()
    }
  }
}

interface ArgsObjInter {
  type: 'string' | 'boolean' | 'number' | 'object' | 'array',
  isRequire: boolean
}

export interface CheckResultInter {
  msg: string
  code: number
}

function checkArgs(type: 'body' | 'query', ctx: KoaContext, args: { [key: string]: ArgsObjInter }): CheckResultInter {
  let requestQuery = ctx.query
  if (type === 'body') {
    requestQuery = ctx.request.body!
  }
  for (let key in args) {
    const _t = args[key].type
    let value = requestQuery[key]
    if (_t === 'number') {
      const v = parseFloat(value)
      // NOTE: 是数字 and 不是 NaN
      if (isNumber(v) && !isNaN(value)) {
        value = v
      }
    }
    const t = `[object ${_t[0].toUpperCase() + _t.slice(1)}]`

    const type = Object.prototype.toString.call(value)
    const isRequire = args[key].isRequire
    // not value and is require
    if (type === 'undefined' && isRequire) {
      return { msg: `${key} parma is require; please check user parmas!`, code: 4001 }
    }

    // is require and type error
    if (isRequire && type !== t) {
      return { msg: `${key} parma is ${type}, but I need ${t}; please check user parmas!`, code: 4001 }
    }

    // not require and type error
    if (value && type !== t) {
      return { msg: `${key} parma is ${type}, but I need ${t}; please check user parmas!`, code: 4001 }
    }

  }
  return { msg: '', code: 0 }
}

function parseArgs(args: string[]): NormalObjInter {
  const obj: NormalObjInter = {}
  for (let q of args) {
    let isRequire = true
    if (q.startsWith('?')) {
      q = q.slice(1)
      isRequire = false
    }
    const [key, type] = q.split(':')
    obj[key] = {
      type, isRequire
    }
  }
  return obj
}

const bindRouteToKoa = () => {
  const _list: Router[] = []
  for (const controller of controllerList) {
    const routeMapList = mapRoute(controller)
    if (routeMapList.length === 0) {
      continue
    }
    for (const r of routeMapList) {
      const { method, path, hander, instance } = r
      const key = method.toLowerCase() as 'get' | 'post'
      console.info(color.blue(`[ bind router method: ${key} path: /api${path}]`))
      _list.push(router[key](`/api${path}`, asyncWrap(instance, hander)))
    }
  }
}

bindRouteToKoa()

export { router }
